package com.example.doipandroidclient.blueTooth.blueTooth;

import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.util.Log;

import com.example.doipandroidclient.AndroidInitializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import core.codec.packet.PacketMessageCodec;
import core.codec.packet.PacketMessageCodecImpl;
import core.doipMessage.DoipMessage;
import core.doipMessage.MessageEnvelope;
import endpoint.doipClient.DoipClientChannel;
import endpoint.doipClient.DoipMessageCallback;

public class BlueToothDoipClient implements DoipClientChannel {

    private Context ctx;
    private String address;
    private final String TAG = "BluetoothDoipClient";

    private BluetoothDevice device;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothSocket targetSocket;
    private PacketMessageCodec codec = new PacketMessageCodecImpl();
    Random random = new Random();
    boolean isConnected = false;

    private HashMap<Integer,DoipMessageCallback> waitingCallback = new HashMap<>();

    private boolean connecting = false;

    public BlueToothDoipClient(Context c){
        ctx = c;
    }

    @Override
    public void close(){
        try {
            targetSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void connect(String url) throws URISyntaxException{
        isConnected = false;
        URI uri = new URI(url);
        this.address = uri.getAuthority();
        try {
            connectBTServer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean isConnected() {
        return isConnected;
    }

    public void sendMessage(DoipMessage message, DoipMessageCallback cb){

        log("try to send message");
        if(device == null){
            Log.e(TAG,"no connection to target repository device");
        }
        try {
////            targetSocket.connect();
//            if(targetSocket == null || !targetSocket.isConnected()){
//                targetSocket = device.createRfcommSocketToServiceRecord(AndroidInitializer.DOIP_SERVICE_UUID);
//                log("reconnect to repository");
//                new ConnectThread(targetSocket).run();
//                while(connecting){
//                    log("wait for connect");
//                    Thread.sleep(500);
//                }
//            }
            while(!isConnected){
                log("not connected, wait for connect.");
                Thread.sleep(500);
            }

            if(message.requestID == 0) message.requestID = random.nextInt();
            log("requestID in message: " + message.requestID);

            OutputStream output = targetSocket.getOutputStream();
            ArrayList<MessageEnvelope> envelopes = codec.MessageToEnvelopes(message);

            waitingCallback.put(message.requestID,cb);

            log("connected: " + targetSocket.isConnected());
            for (MessageEnvelope env : envelopes) {
                log("requestID in envelope: " + env.requestId);
                byte[] envBytes = codec.EnvelopeToBytes(env);
                log("sending bytes length: " + envBytes.length);
                output.write(codec.EnvelopeToBytes(env));
            }
            InputStream input = targetSocket.getInputStream();
            new ResponseHandlerThread(input).start();

        }catch (Exception e){
            e.printStackTrace();
            targetSocket = null;
        }
    };


    private void connectBTServer() throws IOException {

        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null) {
            // 说明此设备不支持蓝牙操作
            log("do not support Bluetooth");
            return;
        }
        if (!bluetoothAdapter.isEnabled()) {
            AlertDialog dialog = new AlertDialog.Builder(ctx).setTitle("提示").setMessage("打开蓝牙再尝试!").create();
            dialog.show();
            return;
        }
        for(BluetoothDevice dev:bluetoothAdapter.getBondedDevices()){
            log("bounded bt address: " + dev.getAddress());
            log("bounded bt name: " + dev.getName());
            log("bounded bt state: " + dev.getType());
        };

        device = bluetoothAdapter.getRemoteDevice(address);
        if(device == null){
            log("cannot find target device. ");
            return;
        }
        log("remote bt address: " + device.getAddress());
        log("remote bt name: " + device.getName());
        log("remote bt state: " + device.getType());

        targetSocket = device.createRfcommSocketToServiceRecord(AndroidInitializer.DOIP_SERVICE_UUID);

        new ConnectThread(targetSocket).start();
    }

    void log(String s){
        Log.d(TAG,s);
    }

    class ConnectThread extends Thread{
        private BluetoothSocket mSocket;

        ConnectThread(BluetoothSocket s) throws IOException {
            mSocket = s;
        }

        @Override
        public synchronized void run(){
            // 关闭发现设备
            bluetoothAdapter.cancelDiscovery();
            if(mSocket.isConnected()) return;
            try{
                log("try to connect device");
                connecting = true;
                mSocket.connect();
                log("connected: " + mSocket.isConnected());
                log("remote device name: " + mSocket.getRemoteDevice().getName());
                log("remote device address: " + mSocket.getRemoteDevice().getAddress());
                isConnected = true;
            }catch(IOException connectException){
                connectException.printStackTrace();
            }finally {
                connecting = false;
            }
        }
    }

    class ResponseHandlerThread extends Thread{
        InputStream input;
        ResponseHandlerThread(InputStream in){
            input = in;
        }

        @Override
        public synchronized void run(){
            log("try to get response:");
            DoipMessage resp = getDoipMessageFromInputStream(input);
            if(resp == null){
                Log.e(TAG,"Unable to get response message");
                return;
            }
            if(waitingCallback.get(resp.requestID) == null){
                Log.e(TAG,"Unable to get callback");
                return;
            }
            waitingCallback.get(resp.requestID).onResult(resp);
            waitingCallback.remove(resp.requestID);
        }

    }
    private DoipMessage getDoipMessageFromInputStream(InputStream in){
        DoipMessage message = null;
        try {
            byte[] envHeader = getBytesFromInputStream(in,24);
            int requestId = (int) ((envHeader[8+3] & 0xFF)
                    | ((envHeader[8+2] & 0xFF)<<8)
                    | ((envHeader[8+1] & 0xFF)<<16)
                    | ((envHeader[8] & 0xFF)<<24));
            log("requestID: " + requestId);
            int contentLength = (int) ((envHeader[23] & 0xFF)
                    | ((envHeader[22] & 0xFF)<<8)
                    | ((envHeader[21] & 0xFF)<<16)
                    | ((envHeader[20] & 0xFF)<<24));
            log("content Length: " + contentLength);

            byte[] envContent = getBytesFromInputStream(in, contentLength);
            return codec.BytesToMessage(envContent,requestId,null);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return message;
    }

    private byte[] getBytesFromInputStream(InputStream in, int len) throws IOException {
        int count = 0;
        while(in.available() < len && count <100){
            count ++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        byte[] bytes = new byte[len];
        in.read(bytes);
        return bytes;
    }
}
